package sdmaker.ast;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.print.attribute.standard.Finishings;

import sdmaker.parser.SetStateData;
import sdmaker.parser.ParseResult;
import sdmaker.parser.SDMakerGenerator;
import sdmaker.parser.SDMakerParser;
import sdmaker.parser.StateData;
import sdmaker.parser.Utils;
import traces.Statement;

import fsa.State;
import fsa.Transition;

public class ASTalt implements AST {
	private AST conseq;
	private AST alter;



	/* ************************ *
	 * ***** CONSTRUCTORS ***** *
	 * ************************ */

	public ASTalt(AST conseq, AST alter){
		this.conseq= conseq;
		this.alter= alter;
	}



	/* ************************ *
	 * ******* TESTERS ******** *
	 * ************************ */

	public static Boolean isAlternative(StateData n, SetStateData treatedStates, SetStateData forbiddenStates){
		List<Transition> successors= n.getSuccesseurs();
		int nbMatchingSuccessor= 0;

		/* ALGORITHM IDEA: 
		 * A state lead to an alternative if it has
		 * more than one successor and two are
		 * opening Transition leading to never seen States.
		 */

		for(Transition succ : successors){
			Statement succSt= (Statement)succ.getTrigger();
			StateData target= StateData.getStateData(succ.getTarget());

			if(forbiddenStates.contains(target)==false&&
					treatedStates.contains(target)==false&&
					Utils.isOpeningStatement(succSt)){
				nbMatchingSuccessor++;
			}
		}

		return (nbMatchingSuccessor > 1);
	}

	public static boolean haveOnlyForbiddenTransition(
			StateData n, StateData originAlter, 
			SetStateData forbiddenStates){

		SetStateData union= 
				forbiddenStates
				.union(originAlter.getAllNextStateData());

		return n.fullParsed(union);
	}

	public static boolean isAltInLoop(
			StateData conseqLastSeen, StateData alterLastSeen, 
			SetStateData alterBranch, SetStateData finalStates, 
			SetStateData forbiddenStates){

		/* IDEA:
		 * Si on peut tjr revenir dans le conseq ou le alt alors
		 * c'est un alt dans un loop
		 */

		SetStateData forConseq= conseqLastSeen.getAllNextStateData().difference(forbiddenStates.union(finalStates));
		SetStateData forAlter= alterLastSeen.getAllNextStateData().difference(forbiddenStates.union(finalStates));

		return forConseq.equals(alterBranch)&&forAlter.equals(alterBranch);
	}



	/* ************************ *
	 * ******** OTHERS ******** *
	 * ************************ */

	//amelioration: eviter duplication code conseq/alter !
	public static void parseAlt(SDMakerGenerator generator, StateData n,
			SetStateData seenStates, SetStateData forbiddenStates){

		SetStateData computedEndsState= 
				computeEndsState(n, generator.getFinalState());


		//CONSEQ VARIABLES
		StateData nextInConseqToParse= n.getSimpleStateData(0);
		List<ParseResult> conseq= new ArrayList<ParseResult>();
		ParseResult lastParsed= null;
		StateData conseqLastSeen= null;
		StateData alterLastSeen= null;
		int nbToGet=0;


		//PARSAGE DE n pour CONSEQ
		do{
			SDMakerParser.parseState(
					generator, 
					nextInConseqToParse, 
					seenStates, 
					forbiddenStates
					.union(computedEndsState));

			if(lastParsed!=generator.getLastParsed()){
				nbToGet++;
				lastParsed=generator.getLastParsed();
				if(generator.getLastParsed()
						.getResultingAST() instanceof ASTloop){

					/* Pour se construire, le loop vide le generator des elements
					 * qu'il contient. Il faut donc faire un retranchement de ce
					 * que l'on doit recupérer en fin. */

					nbToGet= nbToGet - 
							((ASTloop)lastParsed.getResultingAST()).size();
				}

				if(nextInConseqToParse
						.fullParsed(forbiddenStates.union(computedEndsState))){
					nextInConseqToParse= seenStates.firstNotFullParsedFIFO(forbiddenStates.union(n));
				}

				if(nextInConseqToParse!=null){
					conseqLastSeen= nextInConseqToParse;
				}

				if(nextInConseqToParse!=null&&
						haveOnlyForbiddenTransition(
								nextInConseqToParse, n, 
								forbiddenStates.union(computedEndsState))){
					nextInConseqToParse=null;
				}
			}

		}while(nextInConseqToParse!=null);


		for(int i=0; i < nbToGet; i++){
			lastParsed= generator.getLastParsed();
			conseq.add(lastParsed);
			generator.remove(lastParsed);
		}

		//comme on recupere depuis la fin, on est a l'envers
		conseq= Utils.reverse(conseq); 

		/*si on ne retire pas le dernier vu, on risque de reperer une boucle
		  dans ????????????? */
		seenStates.remove(conseqLastSeen);

		/* Si on ne retire pas n des seenStates, on reperera une boucle lors de
		 * son parsage pour alter */
		seenStates.remove(n);



		//ALTER VARIABLES
		StateData nextInAlterToParse= n.getSimpleStateData(1);
		List<ParseResult> alter= new ArrayList<ParseResult>();
		nbToGet= 0;
		lastParsed= null;



		//PARSAGE DE n pour ALTER
		do{
			SDMakerParser.parseState(
					generator, 
					nextInAlterToParse, 
					seenStates, 
					forbiddenStates
					.union(computedEndsState));

			if(lastParsed!=generator.getLastParsed()){
				nbToGet++;
				lastParsed=generator.getLastParsed();
				if(generator.getLastParsed()
						.getResultingAST() instanceof ASTloop){

					/* Pour se construire, le loop vide le generator des elements
					 * qu'il contient. Il faut donc faire un retranchement de ce
					 * que l'on doit recupérer en fin. */

					nbToGet= nbToGet - 
							((ASTloop)lastParsed.getResultingAST()).size();
				}


				if(nextInAlterToParse.fullParsed(
						forbiddenStates.union(computedEndsState))){
					nextInAlterToParse= seenStates.firstNotFullParsedFIFO(
							forbiddenStates.union(n));
				}

				if(nextInAlterToParse!=null){
					alterLastSeen= nextInAlterToParse;
				}

				if(nextInAlterToParse!=null&&
						haveOnlyForbiddenTransition(nextInAlterToParse, n, 
								forbiddenStates.union(computedEndsState))){
					nextInAlterToParse=null;
				}
			}

		}while(nextInAlterToParse!=null);

		for(int i=0; i < nbToGet; i++){
			lastParsed= generator.getLastParsed();
			alter.add(lastParsed);
			generator.remove(lastParsed);
		}

		//comme on recupere depuis la fin, on est a l'envers
		alter= Utils.reverse(alter);



		//CONSTRUCTION FINAL 
		if(computedEndsState.size()>1){
			seenStates.addAll(computedEndsState);
		}

		AST conseqAST= new ASTsequence(Utils.toListAST(conseq));
		AST alterAST= new ASTsequence(Utils.toListAST(alter));
		AST alternative= new ASTalt(conseqAST, alterAST);

		if(isAltInLoop(conseqLastSeen, alterLastSeen, 
				n.getAllNextStateData(), computedEndsState, forbiddenStates)){

			List<AST>body= new ArrayList<AST>();

			body.add(alternative);
			alternative= new ASTloop(body);

			for(StateData stateData: n.getAllNextStateData().getStates()){
				conseqLastSeen.addParsedTransition(stateData);
				alterLastSeen.addParsedTransition(stateData);
			}
		}
		else{ 
			/* On est dans un alt simple, on regarde alors si conseq
			 * ou alter est contenue dans une boucle.Normalement
			 * faire alter puis conseq ou le contraire n'as pas 
			 * d'influence. Je n'ai pas encore mis à jour */
			
			
			if(ASTloop.leadToLoop(alterLastSeen, seenStates, forbiddenStates)){

				/*un ASTloop se sert du dernier element de la boucle pour 
				 * construire son corps. Or il s'attend à ce que cette élément
				 * soit le dernier parsé dans le generator. */
				generator.add(Utils.getLast(alter)); 

				ASTloop.parseLoop(generator, alterLastSeen, seenStates, forbiddenStates);
				alterAST= generator.getLastParsed().getResultingAST();
				generator.removeLast();
			}
			if(ASTloop.leadToLoop(conseqLastSeen, seenStates, forbiddenStates)){

				//meme remarque
				generator.add(Utils.getLast(conseq));

				ASTloop.parseLoop(generator, conseqLastSeen, seenStates, forbiddenStates);
				conseqAST= generator.getLastParsed().getResultingAST();
				generator.removeLast();
			}
			alternative= new ASTalt(conseqAST, alterAST);
		}

		StateData startingState=n;
		StateData finishingState=null;
		AST resultingAST= alternative;
		Transition lastTransition= null;
		ParseResult previousResult= generator.getLastParsed();
		ParseResult nextResult= null;

		seenStates.add(n); //in case alter or/and conseq are empty
		seenStates.remove(conseqLastSeen);

		if(computedEndsState.size()==1){
			StateData finishingConseq= Utils.getLast(conseq).getFinishingState();
			StateData finishingAlter= Utils.getLast(alter).getFinishingState();		
			
			//we don't care if conseq or alter
			finishingState= finishingConseq.getSimpleStateData(computedEndsState.get(0));
			finishingAlter.addParsedTransition(computedEndsState.get(0));

			//we don't care whitch one
			lastTransition= finishingState.getPredecesseur(0); 
		}

		generator.add(new ParseResult(
				startingState, finishingState,
				resultingAST, lastTransition,
				previousResult, nextResult,
				seenStates));
	}

	private static SetStateData computeEndsState(StateData n, SetStateData finalStates){
		SetStateData computedEndState= new SetStateData();
		SetStateData conseqToTreat= new SetStateData();
		SetStateData conseqTreated= new SetStateData();
		SetStateData alterToTreat= new SetStateData();
		SetStateData alterTreated= new SetStateData();
		
		//pour eviter les boucles
		SetStateData forbiddenStates= n.getAllNextStateData();
		
		Boolean finishConseq= false;
		Boolean finishAlter= false;

		conseqToTreat.add(
				StateData.getStateData(n.getSuccesseurs().get(0).getTarget()));
		alterToTreat.add(
				StateData.getStateData(n.getSuccesseurs().get(1).getTarget()));

		while(conseqTreated.intersection(alterTreated).size()==0&&
				(finishConseq==false||finishAlter==false)){

			step(conseqToTreat, conseqTreated, 
					alterToTreat, alterTreated,
					finalStates);

			conseqToTreat= conseqToTreat.difference(forbiddenStates);
			alterToTreat= alterToTreat.difference(forbiddenStates);
			
			finishConseq= finalStates.intersection(conseqTreated).size()>0;
			finishAlter= finalStates.intersection(alterTreated).size()>0;
		}

		computedEndState.addAll(conseqTreated.intersection(alterTreated));
		
		if(computedEndState.size()==0){
			computedEndState.addAll(finalStates.intersection(conseqTreated));
			computedEndState.addAll(finalStates.intersection(alterTreated));
		}

		return computedEndState;
	}

	private static void step(
			SetStateData conseqToTreat,
			SetStateData conseqTreated,
			SetStateData alterToTreat,
			SetStateData alterTreated,
			SetStateData finalStates){
		SetStateData newConseqToTreat= new SetStateData();
		SetStateData newAlterToTreat= new SetStateData();
		Boolean finishConseq= finalStates.intersection(conseqTreated).size()>0;
		Boolean finishAlter= finalStates.intersection(alterTreated).size()>0;

		if(finishConseq==false){
			for(StateData state : conseqToTreat.getStates()){
				for(Transition t : state.getSuccesseurs()){
					newConseqToTreat.add(StateData.getStateData(t.getTarget()));
				}
			}
			conseqTreated.addAll(conseqToTreat);
			conseqToTreat.removeAll();
			conseqToTreat.addAll(newConseqToTreat);
		}

		if(finishAlter==false){
			for(StateData state : alterToTreat.getStates()){
				for(Transition t : state.getSuccesseurs()){
					newAlterToTreat.add(StateData.getStateData(t.getTarget()));
				}
			}

			alterTreated.addAll(alterToTreat);
			alterToTreat.removeAll();
			alterToTreat.addAll(newAlterToTreat);
		}

	}

	public String toString(){
		String result="";

		result+="Alt conseq{\n";
		result+=conseq+"\n";
		result+="} alter {\n";
		result+=alter+"\n";
		result+="}";

		return result;
	}
}
